/*
 *  LogStore
 *                              Hyojun Kim
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <pthread.h>
#include "store.h"

#define LOGSTORE_PORT           (2201)
#define MAXDATALEN              (2048)
#define NUMDIGIT                (8)
#define HEADERLEN               (NUMDIGIT + 1)
#define MAXBUFLEN               (MAXDATALEN + NUMDIGIT + 1)

//#define PRNDATA

pthread_mutex_t glock_logstore;

/* Logger Server thread context  */
typedef struct
{
    pthread_t       thread;             /* thread id            */
    int             socket;             /* socket number        */
    byte            buffer[MAXBUFLEN];  /* communication buffer */
} LOGSVR;

/* translation from / to number */
static unsigned
_str_to_unsigned(byte* p)
{
    char buf[NUMDIGIT + 1];

    strncpy(buf, (char*)p, NUMDIGIT);
    return atoi(buf);
}

static void
_unsigned_to_str(unsigned u, byte* p)
{
    char data[10];

    sprintf(data, "%8d", u);
    memcpy(p, data, NUMDIGIT);
}

/* Socket initialization */
static int  
_init_server_socket(int port)
{
    struct sockaddr_in sin;
    int                sock;
    int                yes;

    /* socket creation */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock != -1)
    {
        yes = 1;

        /* socket option setting */
        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 
                       &yes, sizeof(int)) == -1)
        {
            perror("error");
            close(sock);
            sock = -1;
        }
        else
        {
            bzero(&sin, sizeof(sin));
            sin.sin_family      = AF_INET;
            sin.sin_addr.s_addr = INADDR_ANY;
            sin.sin_port        = htons(port);

            /* bind & listen */
            if ((bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) ||
                (listen(sock, 20)                                 == -1))
            {
                perror("error");
                close(sock);
                sock = -1;
            }
        }
    } 
    return sock;
}

/* Logger thread */
static void*
_server_thread(void* data)
{
    LOGSVR*   svr = (LOGSVR*)data;
    unsigned  length;
    unsigned  dlen;
    unsigned  len;
    unsigned  key;
    int       r;

    printf("++ Server thread starts (%d) ++\n", svr->socket);
    while (1)
    {
        length = recv(svr->socket, svr->buffer, MAXBUFLEN, 0); 
        if (length <= 0) 
        {
            goto out;
        }

        switch (svr->buffer[0])
        {
        /* put command */
        case 'P':
            len = _str_to_unsigned(svr->buffer + 1);

            pthread_mutex_lock(&glock_logstore);
            r = store_put(len, svr->buffer + HEADERLEN, &key);
            pthread_mutex_unlock(&glock_logstore);

/* debugging purpose */
#ifdef PRNDATA
            {
                int i;
                printf("Data:");
                for (i = 0; i < len ;i++)
                {
                    printf("%02x ", svr->buffer[HEADERLEN + i]);
                }
                printf("\n");
            }
#endif
            if (r == 0)
            {
                printf("store_put(len = %d) : key = %08d\n", len, key);
                svr->buffer[0] = 'A';
                _unsigned_to_str(key, svr->buffer + 1);
                dlen = HEADERLEN;
            }
            else
            {
                svr->buffer[0] = 'E';
                dlen = 1;
            }
            break;

        /* get command */
        case 'G':
            key = _str_to_unsigned(svr->buffer + 1);

            pthread_mutex_lock(&glock_logstore);
            r   = store_get(key, MAXDATALEN,
                            svr->buffer + HEADERLEN, &len);
            pthread_mutex_unlock(&glock_logstore);
            if (r == 0)
            {
                printf("store_get(key = %08d) : len = %d\n", key, len);
                svr->buffer[0] = 'D';
                _unsigned_to_str(len, svr->buffer + 1);
                dlen = len + HEADERLEN;
            }
            else
            {
                svr->buffer[0] = 'E';
                dlen = 1;
            }
            break;

        /* remove command */
        case 'R':
            key = _str_to_unsigned(svr->buffer + 1);

            pthread_mutex_lock(&glock_logstore);
            r   = store_remove(key);
            pthread_mutex_unlock(&glock_logstore);
            dlen = 1;
            if (r == 0)
            {
                printf("store_remove(key = %08d)\n", key);
                svr->buffer[0] = 'O';
            }
            else
            {
                svr->buffer[0] = 'E';
            }
            break;

        default:
            dlen = 0;
            break;
        }
        if (dlen)
        {
#ifdef PRNDATA
            {
                int i;
                printf("Reply:");
                for (i = 0; i < dlen ;i++)
                {
                    printf("%02x ", svr->buffer[i]);
                }
                printf("\n");
            }
#endif
            r = send(svr->socket, svr->buffer, dlen, 0);
            if (r == -1) break;
        }
    } while (1);
out:
    printf("-- Server thread has been terminated (%d) --\n", svr->socket); 

    free(data);
    pthread_exit(NULL);
}

int
main(void)
{
    LOGSVR*   svr;
    struct    sockaddr_in pin;
    socklen_t addrlen;
    int       server_sock;
    int       client_sock;
    int       r;

    printf("# LogStore Server\n");

    store_init();
    server_sock = _init_server_socket(LOGSTORE_PORT);

    if (server_sock == -1) return 0;
    pthread_mutex_init(&glock_logstore, NULL);

    /* main loop */
    do
    {
        addrlen = sizeof(pin); 
        client_sock = accept(server_sock, 
                             (struct sockaddr *)&pin,
                             &addrlen);

        if (client_sock != -1)
        {
            /* server thread context allocation */
            svr = (LOGSVR*)malloc(sizeof(LOGSVR));
            if (svr)
            {
                memset(svr, 0, sizeof(LOGSVR));
                svr->socket = client_sock;

                pthread_create(&svr->thread, NULL, _server_thread, svr);
            }
        }
    } while (1);

    pthread_mutex_destroy(&glock_logstore);
    store_free();

    return 0;
}
